#include "PulseClient.h"
#include <QDebug>
#include <QProcess>
#include <cstring>

// 一些辅助宏或函数
static pa_volume_t percentToPaVolume(int percent)
{
    // PA_VOLUME_NORM 通常被定义为 65536 (即 0x10000)
    // 这里简单将 [0..100] 映射到 [0..PA_VOLUME_NORM]
    if (percent <= 0)
        return PA_VOLUME_MUTED;
    if (percent >= 100)
        return PA_VOLUME_NORM;
    return (pa_volume_t)((double)PA_VOLUME_NORM * (double)percent / 100.0);
}

static int paVolumeToPercent(pa_volume_t paVol)
{
    // 将 [0..PA_VOLUME_NORM] 映射回 [0..100]
    if (paVol <= PA_VOLUME_MUTED)
        return 0;
    if (paVol >= PA_VOLUME_NORM)
        return 100;
    return (int)(((double)paVol * 100.0 + PA_VOLUME_NORM / 2) / (double)PA_VOLUME_NORM);
}

//------------------- 构造/析构 -------------------
PulseClient::PulseClient(QString lineinName)
    : m_loop(nullptr)
    , m_api(nullptr)
    , m_ctx(nullptr)
    , m_lineinSourceName(lineinName)
{
    connect(&m_thread, &QThread::started, this, [=]() {
        m_loop = pa_mainloop_new();
        if (!m_loop) {
            qWarning() << "Failed to create pa_mainloop.";
            return;
        }
        m_api = pa_mainloop_get_api(m_loop);

        m_ctx = pa_context_new(m_api, "PulseClient");
        if (!m_ctx) {
            qWarning() << "Failed to create pa_context.";
            return;
        }

        pa_context_set_state_callback(
            m_ctx, [](pa_context* c, void* userdata) {
                PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
                if (!self)
                    return;
                self->onContextStateChange(pa_context_get_state(c));
            },
            this);

        pa_context_connect(m_ctx, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr);

        int ret = 0;
        qDebug() << "start running" << QThread::currentThreadId();
        pa_mainloop_run(m_loop, &ret);
        qDebug() << "pa_mainloop_run quit with ret =" << ret;
    });

    qDebug() << "Instance id: " << QThread::currentThreadId();
    moveToThread(&m_thread);
    start();
}

PulseClient::~PulseClient()
{
    // 停止线程并清理资源
    if (m_loop) {
        pa_mainloop_quit(m_loop, 0);
    }
    if (m_thread.isRunning()) {
        m_thread.quit();
        m_thread.wait();
    }
    if (m_ctx) {
        pa_context_unref(m_ctx);
        m_ctx = nullptr;
    }
    if (m_loop) {
        pa_mainloop_free(m_loop);
        m_loop = nullptr;
    }
}

void PulseClient::appendWhiteList(QString k, QString v)
{
    m_muteWhiteList[k] = v;
}

void PulseClient::setDefaultOutputDevice(QString name)
{
    if (!isReady())
        return;

    auto* op = pa_context_set_default_sink(
        m_ctx, name.toStdString().c_str(),
        [](pa_context*, int success, void*) {
            qDebug() << "set-default-output:" << success;
        },
        this);

    if (op)
        pa_operation_unref(op);
}

void PulseClient::setDefaultInputDevice(QString name)
{
    if (!isReady())
        return;

    auto* op = pa_context_set_default_source(
        m_ctx, name.toStdString().c_str(),
        [](pa_context*, int success, void*) {
            qDebug() << "set-default-input:" << success;
        },
        this);

    if (op)
        pa_operation_unref(op);
}

// 启动 PulseClient
void PulseClient::start()
{
    m_thread.start();
}

void PulseClient::setOutputVolume(QString name, quint32 vol)
{
    if (!isReady())
        return;
    pa_volume_t v = percentToPaVolume(vol);
    double* volume = new double(v);
    pa_operation* op = pa_context_get_sink_info_by_name(
        m_ctx, name.toStdString().c_str(),
        [](pa_context* c, const pa_sink_info* i, int eol, void* userdata) {
            if (i && !eol) {
                double* vol = static_cast<double*>(userdata);
                pa_cvolume* cvol = pa_cvolume_scale(const_cast<pa_cvolume*>(&(i->volume)), *vol);
                pa_cvolume_set_balance(const_cast<pa_cvolume*>(&(i->volume)), &(i->channel_map), 0.0);
                if (cvol) {
                    pa_operation* o = pa_context_set_sink_volume_by_name(
                        c, i->name, cvol,
                        [](pa_context*, int success, void*) {
                            qDebug() << "setOutputVolume success: " << success;
                        },
                        nullptr);
                    if (o)
                        pa_operation_unref(o);
                }
            }
        },
        volume);
    if (op) {
        pa_operation_set_state_callback(
            op, [](pa_operation* op, void* userdata) {
                if (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
                    return;
                double* data = static_cast<double*>(userdata);
                delete data;
                pa_operation_unref(op);
            },
            volume);
    } else {
        delete volume;
    }
}

void PulseClient::setInputVolume(QString name, quint32 vol)
{
    if (!isReady())
        return;
    pa_volume_t v = percentToPaVolume(vol);
    double* volume = new double(v);
    pa_operation* op = pa_context_get_source_info_by_name(
        m_ctx, name.toStdString().c_str(),
        [](pa_context* c, const pa_source_info* i, int eol, void* userdata) {
            if (i && !eol) {
                double* vol = static_cast<double*>(userdata);
                pa_cvolume* cvol = pa_cvolume_scale(const_cast<pa_cvolume*>(&(i->volume)), *vol);
                pa_cvolume_set_balance(const_cast<pa_cvolume*>(&(i->volume)), &(i->channel_map), 0.0);
                if (cvol) {
                    pa_operation* o = pa_context_set_source_volume_by_name(
                        c, i->name, cvol,
                        [](pa_context*, int success, void*) {
                            qDebug() << "setInputVolume success: " << success;
                        },
                        nullptr);
                    if (o)
                        pa_operation_unref(o);
                }
            }
        },
        volume);
    if (op) {
        pa_operation_set_state_callback(
            op, [](pa_operation* op, void* userdata) {
                if (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
                    return;
                double* data = static_cast<double*>(userdata);
                delete data;
                pa_operation_unref(op);
            },
            volume);
    } else {
        delete volume;
    }
}

void PulseClient::monitorOutputVolumeLevel(bool onoff)
{
    if (onoff) {
        pa_operation* op = pa_context_get_server_info(
            m_ctx, [](pa_context*, const pa_server_info* info, void* userdata) {
                PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
                if (self && info) {
                    QString monitor_source_name(info->default_sink_name);
                    monitor_source_name += ".monitor";
                    self->m_sinkStream = self->pcmMonitor(monitor_source_name);
                }
            },
            this);
        if (op) {
            pa_operation_unref(op);
        }
    } else {
        stopPcmMonitor(m_sinkStream);
    }
}

void PulseClient::monitorInputVolumeLevel(bool onoff)
{
    if (onoff) {
        pa_operation* op = pa_context_get_server_info(
            m_ctx, [](pa_context*, const pa_server_info* info, void* userdata) {
                PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
                if (self && info) {
                    QString monitor_source_name(info->default_source_name);
                    self->m_sourceStream = self->pcmMonitor(monitor_source_name);
                }
            },
            this);
        if (op) {
            pa_operation_unref(op);
        }
    } else {
        stopPcmMonitor(m_sourceStream);
    }
}

void PulseClient::stopPcmMonitor(pa_stream* s)
{
    if (s) {
        pa_stream_disconnect(s);
        pa_stream_unref(s);
        s = nullptr;
    }
}

pa_stream* PulseClient::pcmMonitor(QString sourceName)
{
    qDebug() << __func__ << sourceName;
    pa_sample_spec ss;
    ss.format = PA_SAMPLE_FLOAT32;
    ss.rate = 10;
    ss.channels = 1;

    pa_stream* peak = pa_stream_new(m_ctx, "PCMMonitor", &ss, nullptr);
    if (!peak) {
        qDebug() << "Failed to create PCMMonitor stream: " << pa_strerror(pa_context_errno(m_ctx));
        return peak;
    }
    pa_stream_set_read_callback(
        peak, [](pa_stream* p, size_t nbytes, void* userdata) {
            PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
            const void* data;
            if (pa_stream_peek(p, &data, &nbytes) < 0) {
                qDebug() << "peek error: " << pa_strerror(pa_context_errno(pa_stream_get_context(p)));
                return;
            }
            if (!data) {
                qDebug() << "222";
                pa_stream_drop(p);
                return;
            }
            float peakValue = *static_cast<const float*>(data);
            //            float peakValue = ((const float*)data)[nbytes / sizeof(float) - 1];
            bool isOutput = false;
            QString deviceName = pa_stream_get_device_name(p);
            if (deviceName.endsWith(".monitor")) {
                isOutput = true;
            }
            //            qDebug() << "isoutput:" << isOutput << "peakV:" << peakValue;
            emit self->volumeMeter(isOutput, peakValue);
            pa_stream_drop(p);
        },
        this);

    pa_buffer_attr bufattr;
    bufattr.maxlength = (uint32_t)-1;
    bufattr.tlength = (uint32_t)-1;
    bufattr.prebuf = (uint32_t)-1; // -1 = Server chooses.
    bufattr.minreq = (uint32_t)-1;
    bufattr.fragsize = sizeof(float); //峰值检测时，fragsize必须是float大小

    pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_PEAK_DETECT);
    if (pa_stream_connect_record(peak, sourceName.toStdString().c_str(), &bufattr, flags) < 0) {
        qWarning() << "Failed to connect monitor stream";
        pa_stream_unref(peak);
        return nullptr;
    }
    return peak;
}

//------------------- slots 接口实现 -------------------

void PulseClient::startPlayback(QString file)
{
    // 这里为了简单，使用外部命令 paplay 来播放音频文件
    // 真实项目中，可以自己使用 pa_stream API 来做更精细的播放
    if (file.isEmpty()) {
        return;
    }
    stopPlayback(); // 确保上一次播放已停止

    // 启动一个进程去调用 paplay
    QProcess* proc = new QProcess(this);
    connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
        this, [proc](int, QProcess::ExitStatus) {
            proc->deleteLater();
        });
    proc->start("paplay", QStringList() << file);
}

void PulseClient::stopPlayback()
{
    // 简单的做法：杀掉所有名为 paplay 的进程，或维护一个进程指针自己 kill
    // 这里演示使用 pkill paplay（粗暴）
    QProcess::execute("pkill", QStringList() << "paplay");
}

void PulseClient::startRecord(QString savefile)
{
    if (savefile.isEmpty()) {
        return;
    }
    stopRecord(); // 先停止之前的录音

    // 使用 parecord 做示例，同样可以使用 pa_stream 做更灵活的操作
    QProcess* proc = new QProcess(this);
    connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
        this, [proc](int, QProcess::ExitStatus) {
            proc->deleteLater();
        });
    // 假设录制成 wav 格式
    proc->start("parecord", QStringList() << "--file-format=wav" << savefile);
}

void PulseClient::stopRecord()
{
    // 类似，杀掉 parecord
    QProcess::execute("pkill", QStringList() << "parecord");
}

void PulseClient::getAllDevices()
{
    pa_context_get_sink_info_list(
        m_ctx, [](pa_context*, const pa_sink_info* i, int eol, void* userdata) {
            PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
            if (i && !eol) {
                PulseDevice pd;
                pd.name = i->name;
                pd.desc = i->description;
                pd.index = i->index;
                pd.volume = pa_cvolume_max(&(i->volume));
                emit self->getAllDevicesCB(true, pd);
                //                self->m_sinks.append(pd);
                qDebug() << i->name;
            } else if (eol > 0) {
                //                emit self->allDevices(true, self->m_sinks);
                //                self->m_sinks.clear();
            }
        },
        this);
    pa_context_get_source_info_list(
        m_ctx, [](pa_context*, const pa_source_info* i, int eol, void* userdata) {
            PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
            if (i && !eol) {
                PulseDevice pd;
                pd.name = i->name;
                pd.desc = i->description;
                pd.index = i->index;
                pd.volume = pa_cvolume_max(&(i->volume));
                emit self->getAllDevicesCB(false, pd);
                //                self->m_sources.append(pd);
                qDebug() << i->name;
            } else if (eol > 0) {
                //                emit self->allDevices(false, self->m_sources);
                //                self->m_sources.clear();
            }
        },
        this);
}

void PulseClient::muteSystemSource(bool mute)
{
    if (!isReady())
        return;
    m_muteSystemSource = mute;

    struct CallbackData {
        PulseClient* pd;
        bool mute;
    };

    CallbackData* data = new CallbackData { this, mute };

    auto* op = pa_context_get_sink_input_info_list(
        m_ctx,
        [](pa_context* c, const pa_sink_input_info* i, int eol, void* userdata) {
            if (eol < 0) {
                qDebug() << "error: " << pa_strerror(pa_context_errno(c));
                return;
            }
            if (eol > 0) {
                return;
            }
            // mute playback stream
            CallbackData* cbdata = static_cast<CallbackData*>(userdata);
            if (i->mute != cbdata->mute) {
                QString role = pa_proplist_gets(i->proplist, "media.role");
                QString app = pa_proplist_gets(i->proplist, "application.process.binary");
                if (role == "LineIn" || app == "bsp")
                    return;
                auto* op = pa_context_set_sink_input_mute(
                    c, i->index, cbdata->mute,
                    [](pa_context*, int success, void*) {
                        qDebug() << "mute playback stream:" << success;
                    },
                    nullptr);
                if (op)
                    pa_operation_unref(op);
            }
        },
        data);

    if (op) {
        pa_operation_set_state_callback(
            op, [](pa_operation* op, void* userdata) {
                if (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
                    return;
                CallbackData* data = static_cast<CallbackData*>(userdata);
                delete data;
                pa_operation_unref(op);
            },
            data);
    }
}

void PulseClient::playbackLineIn(bool play)
{
    if (!isReady())
        return;
    struct CallbackData {
        PulseClient* pd;
        bool mute;
    };

    CallbackData* data = new CallbackData { this, play };

    pa_operation* op = pa_context_get_module_info_list(
        m_ctx,
        [](pa_context* c, const pa_module_info* i, int eol, void* userdata) {
            static int index = -1;
            CallbackData* cbdata = static_cast<CallbackData*>(userdata);
            if (eol > 0) {
                if (index < 0) {
                    // not found loopback module
                    QString lineinsource("source=" + cbdata->pd->m_lineinSourceName);
                    QString arguments
                        = "%1"
                          " source_dont_move=true"
                          " sink_input_properties=media.role='LineIn'";
                    auto op = pa_context_load_module(
                        c, "module-loopback", arguments.arg(lineinsource).toStdString().c_str(),
                        [](pa_context*, uint32_t idx, void*) {
                            qDebug() << "Load loopback index:" << idx;
                        },
                        nullptr);
                    if (op)
                        pa_operation_unref(op);
                }
                index = -1;
                return;
            }
            if (eol < 0) {
                qWarning() << "PulseAudio [Warning]: Failed to list module info. "
                           << pa_strerror(pa_context_errno(c));
                return;
            }
            if (!i)
                return;
            if (QString(i->name) != "module-loopback")
                return;

            qDebug() << "M:" << i->name << i->argument;
            if (QString(i->argument).contains("Linein")) {
                index = i->index;
                auto* op = pa_context_set_source_mute_by_name(
                    cbdata->pd->m_ctx,
                    cbdata->pd->m_lineinSourceName.toStdString().c_str(),
                    cbdata->mute,
                    nullptr, nullptr);
                if (op)
                    pa_operation_unref(op);
            }
        },
        data);

    if (op) {
        pa_operation_set_state_callback(
            op, [](pa_operation* op, void* userdata) {
                if (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
                    return;
                CallbackData* data = static_cast<CallbackData*>(userdata);
                delete data;
                pa_operation_unref(op);
            },
            data);
    }
}

bool PulseClient::isReady()
{
    qDebug() << "readythread: " << QThread::currentThreadId();
    if (!m_ctx) {
        qDebug() << "context is null";
        return false;
    }
    bool ready = pa_context_get_state(m_ctx) == PA_CONTEXT_READY;
    qDebug() << __func__ << ready;
    return ready;
}

void PulseClient::onContextStateChange(pa_context_state_t state)
{
    switch (state) {
    case PA_CONTEXT_READY: {
        // 上下文就绪后，获取服务器信息
        pa_operation* op = pa_context_get_server_info(
            m_ctx, [](pa_context*, const pa_server_info* info, void* userdata) {
                PulseClient* self = reinterpret_cast<PulseClient*>(userdata);
                if (self && info) {
                    self->onServerInfo(info);
                }
            },
            this);
        if (op) {
            qDebug() << "ok get server info";
            pa_operation_unref(op);
        }

        // 订阅 sink/source 相关事件
        pa_context_set_subscribe_callback(m_ctx, &PulseClient::subscribeCallback, this);
        pa_operation* subOp = pa_context_subscribe(m_ctx,
            (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK_INPUT),
            nullptr,
            nullptr);
        if (subOp) {
            pa_operation_unref(subOp);
        }

        qDebug() << "send singal ready";
        // 发出就绪状态信号
        emit stateChanged(PulseClient::Ready);
        break;
    }
    case PA_CONTEXT_FAILED:
    case PA_CONTEXT_TERMINATED: {
        if (m_loop) {
            pa_mainloop_quit(m_loop, 0);
        }
        break;
    }
    default:
        break;
    }
}

void PulseClient::onServerInfo(const pa_server_info* info)
{
    if (!info)
        return;
    // 保存默认 sink/source 名
    m_defaultSinkName = QString::fromUtf8(info->default_sink_name ? info->default_sink_name : "");
    m_defaultSourceName = QString::fromUtf8(info->default_source_name ? info->default_source_name : "");
    qDebug() << "PulseAudio Service Info: ";
    qDebug() << "[default sink]: " << m_defaultSinkName;
    qDebug() << "[default source]: " << m_defaultSourceName;

    getAllDevices();
}

void PulseClient::notify(Event ev, uint32_t index)
{
    pa_operation* op;

    struct CallbackData {
        PulseClient* pd;
        Event event;
    };

    CallbackData* data = new CallbackData { this, ev };

    if (ev == Event::E_Sink_Add || ev == E_Sink_Remove || ev == E_Sink_Vol_Changed) {
        op = pa_context_get_sink_info_by_index(
            m_ctx, index,
            [](pa_context*, const pa_sink_info* i, int eol, void* userdata) {
                if (i && !eol) {
                    CallbackData* cbdata = static_cast<CallbackData*>(userdata);
                    PulseDevice info;
                    info.name = i->name;
                    info.desc = i->description;
                    info.index = i->index;
                    info.volume = paVolumeToPercent(pa_cvolume_max(&(i->volume)));
                    //                    char cv[PA_CVOLUME_SNPRINT_MAX];
                    //                    qDebug() << pa_cvolume_snprint(cv, sizeof(cv), &(i->volume));
                    emit cbdata->pd->subEvent(cbdata->event, info);
                }
            },
            data);
    } else if (ev == Event::E_Source_Add || ev == E_Source_Remove || E_Source_Vol_Changed) {
        op = pa_context_get_source_info_by_index(
            m_ctx, index,
            [](pa_context*, const pa_source_info* i, int eol, void* userdata) {
                if (i && !eol) {
                    CallbackData* cbdata = static_cast<CallbackData*>(userdata);
                    PulseDevice info;
                    info.name = i->name;
                    info.desc = i->description;
                    info.index = i->index;
                    info.volume = paVolumeToPercent(pa_cvolume_max(&(i->volume)));
                    emit cbdata->pd->subEvent(cbdata->event, info);
                }
            },
            data);
    } else if (ev == Event::E_Sink_Input_Add) {
        op = pa_context_set_sink_input_mute(m_ctx, index, m_muteSystemSource, nullptr, nullptr);
    }

    if (op) {
        pa_operation_set_state_callback(
            op, [](pa_operation* op, void* userdata) {
                if (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
                    return;
                CallbackData* data = static_cast<CallbackData*>(userdata);
                delete data;
                pa_operation_unref(op);
            },
            data);
    }
}

void PulseClient::subscribeCallback(pa_context* c,
    pa_subscription_event_type_t t,
    uint32_t index,
    void* userdata)
{
    Q_UNUSED(c)
    PulseClient* self = static_cast<PulseClient*>(userdata);

    int operation = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
    case PA_SUBSCRIPTION_EVENT_SINK: {
        // Sink 相关
        if (operation == PA_SUBSCRIPTION_EVENT_NEW) {
            self->notify(E_Sink_Add, index);
        } else if (operation == PA_SUBSCRIPTION_EVENT_CHANGE) {
            self->notify(E_Sink_Vol_Changed, index);
        } else if (operation == PA_SUBSCRIPTION_EVENT_REMOVE) {
            self->notify(E_Sink_Remove, index);
        }
        break;
    }
    case PA_SUBSCRIPTION_EVENT_SOURCE: {
        // Source 相关
        if (operation == PA_SUBSCRIPTION_EVENT_NEW) {
            self->notify(E_Source_Add, index);
        } else if (operation == PA_SUBSCRIPTION_EVENT_CHANGE) {
            self->notify(E_Source_Vol_Changed, index);
        } else if (operation == PA_SUBSCRIPTION_EVENT_REMOVE) {
            self->notify(E_Source_Remove, index);
        }
        break;
    }
    case PA_SUBSCRIPTION_EVENT_SINK_INPUT: {
        if (operation == PA_SUBSCRIPTION_EVENT_NEW) {
            self->notify(E_Sink_Input_Add, index);
        }
        break;
    }
    default:
        break;
    }
}
